package com.yeskery.nut.application.nio;

import com.yeskery.nut.application.NutApplication;
import com.yeskery.nut.application.NutServerConfigure;
import com.yeskery.nut.application.SecureServerContext;
import com.yeskery.nut.core.*;
import com.yeskery.nut.http.nio.NioRequest;
import com.yeskery.nut.http.nio.NioResponse;

import javax.net.ssl.SSLEngine;
import javax.net.ssl.SSLEngineResult;
import javax.net.ssl.SSLException;
import javax.net.ssl.SSLSession;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.util.logging.Logger;

/**
 * NIO 方式下的 Nut Server 类
 * 如果需要实现其它类型的证书，可以实现 {@link SecureServerContext}
 * 接口，并以 {@link #NioSecureNutServer(SecureServerContext)} 构造
 * 方法创建 Nut Server 类。
 *
 * @author sprout
 * 2019-03-22 11:26
 * @version 1.0
 *
 * @see com.yeskery.nut.application.NutServer
 * @see com.yeskery.nut.application.nio.NioNutServer
 * @see SecureServerContext
 *
 * @deprecated 推荐使用 {@link com.yeskery.nut.application.sun.SunSecureNutServer}
 * 或 {@link com.yeskery.nut.application.netty.NettySecureNutServer}.
 */
@Deprecated
public class NioSecureNutServer extends NioNutServer {

	/** 日志对象 */
	private static final Logger logger = Logger.getLogger(NioSecureNutServer.class.getName());

	/** SecureServerContext 对象 */
	private final SecureServerContext secureServerContext;

	/**
	 * 以自定义形式的证书构造安全的 Nut Server 类
	 * @param secureServerContext 安全的上下文容器
	 */
	public NioSecureNutServer(SecureServerContext secureServerContext) {
		if (secureServerContext == null) {
			throw new NutException("SecureServerContext Interface Must Not Be Null.");
		}
		this.secureServerContext = secureServerContext;
	}

	@Override
	public void startServer(NutServerConfigure nutServerConfigure) throws Exception {
		//启动 Server Socket
		Selector selector = Selector.open();
		ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
		serverSocketChannel.socket().bind(new InetSocketAddress(nutServerConfigure.getPort()));
		serverSocketChannel.configureBlocking(false);
		serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
		logger.info(getServerStartedTip(nutServerConfigure.getPort(), "NIO", true));

		// SSLEngine 对象
		SSLEngine sslEngine = secureServerContext.getSslContext().createSSLEngine();
		sslEngine.setUseClientMode(false);

		ByteBufferData byteBufferData = new ByteBufferData(sslEngine);
		monitor(selector, nutServerConfigure.getNutApplication(), nutServerConfigure.getServerContext(),
				nutServerConfigure.getDispatcher(), nutServerConfigure.getSessionManager(),
				nutServerConfigure.getServerRequestConfiguration(), byteBufferData);
	}

	@Override
	protected void doAccept(NutApplication nutApplication, Selector selector, SelectionKey key, Object object) throws Exception {
		SocketChannel socketChannel = ((ServerSocketChannel)key.channel()).accept();
		doHandshake(socketChannel, selector, object);
	}

	@Override
	protected void doRead(NutApplication nutApplication, SelectionKey key, ServerContext serverContext, Dispatcher dispatcher,
						  SessionManager sessionManager, ServerRequestConfiguration serverRequestConfiguration, Object object) throws Exception {
		ByteBufferData byteBufferData = (ByteBufferData) object;
		SocketChannel socketChannel = (SocketChannel) key.channel();
		byteBufferData.netInData.clear();
		socketChannel.read(byteBufferData.netInData);
		if (byteBufferData.netInData.position() <= 0) {
			return;
		}
		byteBufferData.netInData.flip();
		SSLEngineResult engineResult = byteBufferData.sslEngine.unwrap(byteBufferData.netInData, byteBufferData.appInData);
		if (engineResult.getStatus() == SSLEngineResult.Status.BUFFER_OVERFLOW) {
			byteBufferData.appInData.clear();
			engineResult = byteBufferData.sslEngine.unwrap(byteBufferData.netInData, byteBufferData.appInData);
		}
		byteBufferData.netInData.compact();
		if (engineResult.getStatus() == SSLEngineResult.Status.OK) {
			byteBufferData.appInData.flip();
			//执行请求响应处理
			Request request = new NioRequest(socketChannel, byteBufferData.appInData.array(), serverContext,
					sessionManager, serverRequestConfiguration);
			NioResponse response = new NioResponse(nutApplication);
			//请求执行的方法
			process(request, response, dispatcher, sessionManager);
			key.attach(response.getBytes());
			key.interestOps(SelectionKey.OP_WRITE);
		}
	}

	@Override
	protected void doWrite(SelectionKey key, Object object) throws Exception {
		byte[] data = (byte[]) key.attachment();
		if (data == null || data.length == 0) {
			return;
		}
		ByteBufferData byteBufferData = (ByteBufferData) object;
		SocketChannel socketChannel = (SocketChannel) key.channel();
		ByteBuffer outNetByteBuffer = ByteBuffer.allocate(byteBufferData.appInData.capacity() * 2);
		ByteBuffer outAppByteBuffer = ByteBuffer.wrap(data);
		outAppByteBuffer.flip();
		SSLEngineResult engineResult = byteBufferData.sslEngine.wrap(outAppByteBuffer, outNetByteBuffer);
		if (engineResult.getStatus() != SSLEngineResult.Status.OK) {
			outNetByteBuffer.clear();
			byteBufferData.sslEngine.wrap(outAppByteBuffer, outNetByteBuffer);
		}
		//写回数据
		outNetByteBuffer.flip();
		socketChannel.write(outNetByteBuffer);
		key.interestOps(SelectionKey.OP_READ);
	}

	/** 握手的操作方法 */
	private void doHandshake(SocketChannel socketChannel, Selector selector, Object object) throws IOException {
		ByteBufferData byteBufferData = (ByteBufferData) object;
		SSLEngine sslEngine = byteBufferData.sslEngine;
		sslEngine.beginHandshake();
		SSLEngineResult.HandshakeStatus hsStatus;
		while (!byteBufferData.handshakeDone) {
			hsStatus = sslEngine.getHandshakeStatus();
			switch (hsStatus) {
				case NEED_TASK:
					doTask(byteBufferData);
					break;
				case NEED_UNWRAP:
					byteBufferData.netInData.clear();
					socketChannel.read(byteBufferData.netInData);
					byteBufferData.netInData.flip();
					byteBufferData.appInData.clear();
					doUnwrap(byteBufferData);
					break;
				case NEED_WRAP:
					byteBufferData.netOutData.clear();
					doWrap(byteBufferData);
					socketChannel.write(byteBufferData.netOutData);
					byteBufferData.netOutData.clear();
					break;
				case NOT_HANDSHAKING:
					byteBufferData.handshakeDone = true;
					socketChannel.configureBlocking(false);
					socketChannel.register(selector, SelectionKey.OP_READ);
					break;
				default:
			}
		}

	}

	/** 执行计算任务的方法 */
	private void doTask(ByteBufferData byteBufferData) {
		Runnable runnable;
		while ((runnable = byteBufferData.sslEngine.getDelegatedTask()) != null) {
			runnable.run();
		}
	}

	/** 执行解包数据的方法 */
	private void doUnwrap(ByteBufferData byteBufferData) throws SSLException {
		SSLEngineResult engineResult;
		do{
			engineResult = byteBufferData.sslEngine.unwrap(byteBufferData.netInData, byteBufferData.appInData);
			doTask(byteBufferData);
		}while(engineResult.getStatus() == SSLEngineResult.Status.OK
				&& byteBufferData.sslEngine.getHandshakeStatus() ==  SSLEngineResult.HandshakeStatus.NEED_UNWRAP
				&& engineResult.bytesProduced() == 0);
		if (byteBufferData.appInData.position() == 0 && engineResult.getStatus() == SSLEngineResult.Status.OK
				&& byteBufferData.netInData.hasRemaining()) {
			byteBufferData.sslEngine.unwrap(byteBufferData.netInData, byteBufferData.appInData);
		}
		byteBufferData.netInData.compact();
		byteBufferData.appInData.flip();
	}

	/** 执行打包数据的方法 */
	private void doWrap(ByteBufferData byteBufferData) throws SSLException{
		byteBufferData.sslEngine.wrap(byteBufferData.appOutData, byteBufferData.netOutData);
		doTask(byteBufferData);
		byteBufferData.netOutData.flip();
	}

	/**
	 * 用于保存缓存字节数组信息的对象
	 * @author sprout
	 * 2019-04-16 17:11
	 * @version 1.0
	 */
	class ByteBufferData {
		/** 网络输入字节缓存对象 */
		ByteBuffer appInData;
		/** 网络输出字节缓存对象 */
		ByteBuffer appOutData;
		/** 应用输入字节缓存对象 */
		ByteBuffer netInData;
		/** 应用输出字节缓存对象 */
		ByteBuffer netOutData;
		/** SSLEngine */
		SSLEngine sslEngine;
		/** SSL 会话信息 */
		SSLSession sslSession;
		/** 是否握手成功 */
		boolean handshakeDone;

		/**
		 * 构建一个 {@link ByteBufferData}
		 * @param sslEngine SSLEngine
		 */
		 ByteBufferData(SSLEngine sslEngine) {
		 	this.sslEngine = sslEngine;
		 	sslSession = sslEngine.getSession();
		 	appInData = ByteBuffer.allocate(sslSession.getApplicationBufferSize());
			appOutData = ByteBuffer.allocate(sslSession.getApplicationBufferSize());
			netInData = ByteBuffer.allocate(sslSession.getPacketBufferSize());
			netOutData = ByteBuffer.allocate(sslSession.getPacketBufferSize());
		}
	}
}
